library(tidyverse)         # for graphing and data cleaning
library(tidymodels)        # for modeling
library(stacks)            # for stacking models
library(naniar)            # for analyzing missing values
library(lubridate)         # for date manipulation
library(moderndive)        # for King County housing data
library(vip)               # for variable importance plots
library(DALEX)             # for model interpretation
library(DALEXtra)          # for extension of DALEX
library(patchwork)         # for combining plots nicely
data("lending_club")

Put it on GitHub!

Here is my GitHub link.

Modeling

We’ll be using the lending_club dataset from the modeldata library, which is part of tidymodels. The outcome we are interested in predicting is Class. And according to the dataset’s help page, its values are “either ‘good’ (meaning that the loan was fully paid back or currently on-time) or ‘bad’ (charged off, defaulted, of 21-120 days late)”.

Tasks

  1. Explore the data, concentrating on examining distributions of variables and examining missing values.
lending_club %>% 
  ggplot(aes(x = funded_amnt)) +
  geom_density() + 
  facet_wrap(vars(Class))

lending_club %>% 
  ggplot(aes(x = int_rate)) +
  geom_density() + 
  facet_wrap(vars(Class))

lending_club %>% 
  ggplot(aes(x = annual_inc)) +
  geom_density() + 
  facet_wrap(vars(Class))

lending_club %>% 
  ggplot(aes(x = addr_state, fill = Class)) +
  geom_bar(position = "fill")

lending_club %>% 
  count(Class)
lending_club %>% 
  group_by(addr_state) %>% 
  summarize(count = n()) %>% 
  arrange(desc(count))
  1. Do any data cleaning steps that need to happen before the model is build. For example, you might remove any variables that mean the same thing as the response variable (not sure if that happens here), get rid of rows where all variables have missing values, etc.
create_more_bad <- lending_club %>% 
  filter(Class == "bad") %>% 
  sample_n(size = 3000, replace = TRUE)

lending_club_mod <- lending_club %>% 
  bind_rows(create_more_bad)
  1. Split the data into training and test, putting 75% in the training data.
set.seed(494)

lending_split <- initial_split(lending_club_mod,
                             prop = 0.75)
lending_train <- training(lending_split)
lending_test  <- testing(lending_split)
  1. Set up the recipe and the pre-processing steps to build a lasso model. Some steps you should take:
  • Make all integer variables numeric (I’d highly recommend using step_mutate_at() or this will be a lot of code). We’ll want to do this for the model interpretation we’ll do later.
  • Think about grouping factor variables with many levels.
  • Make categorical variables dummy variables (make sure NOT to do this to the outcome variable).
  • Normalize quantitative variables.
lending_recipe <- recipe(Class ~ .,
                         data = lending_train) %>% 
  step_rm(acc_now_delinq, delinq_amnt) %>% 
  step_mutate_at(all_numeric(),
                 fn = ~as.numeric(.)) %>% 
  # step_mutate(annual_inc =
  #             case_when(annual_inc <= 9875                          ~ 10,
  #                       annual_inc > 9875   && annual_inc <= 40125  ~ 12,
  #                       annual_inc > 40125  && annual_inc <= 85525  ~ 22,
  #                       annual_inc > 85525  && annual_inc <= 163300 ~ 24,
  #                       annual_inc > 163300 && annual_inc <= 207350 ~ 32,
  #                       annual_inc > 207350 && annual_inc <= 518400 ~ 35,
  #                       annual_inc > 518400                         ~ 37)) %>%
  # step_mutate(annual_inc = as.factor(annual_inc)) %>% 
  step_normalize(all_predictors(), -all_nominal()) %>% 
  step_dummy(all_nominal(), -all_outcomes())

lending_recipe %>% 
  prep(lending_train) %>% 
  juice()
  1. Set up the LASSO model and workflow. We will tune the penalty parameter.
lasso_lending_mod <- logistic_reg(mixture = 1) %>%
  set_engine("glmnet") %>% 
    set_args(penalty = tune()) %>% 
      set_mode("classification")

lasso_lending_mod
## Logistic Regression Model Specification (classification)
## 
## Main Arguments:
##   penalty = tune()
##   mixture = 1
## 
## Computational engine: glmnet
lending_workflow <- workflow() %>%
  add_recipe(lending_recipe) %>%
    add_model(lasso_lending_mod)

lending_workflow
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
## 
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 4 Recipe Steps
## 
## ● step_rm()
## ● step_mutate_at()
## ● step_normalize()
## ● step_dummy()
## 
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
## 
## Main Arguments:
##   penalty = tune()
##   mixture = 1
## 
## Computational engine: glmnet
  1. Set up the model tuning for the penalty parameter. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Find the accuracy and area under the roc curve for the model with the best tuning parameter. Use 5-fold cv.
set.seed(494)

lending_cv <- vfold_cv(lending_train, v = 5)

lending_lasso_pen_grid <- grid_regular(penalty(), levels = 10)

ctrl_grid <- control_stack_grid()

lending_lasso_tune <- lending_workflow %>% 
  tune_grid(resamples = lending_cv,
            grid = lending_lasso_pen_grid,
            control = ctrl_grid)
lending_lasso_tune %>% 
  show_best(metric = "accuracy")
lending_lasso_tune %>% 
  collect_metrics() %>% 
  filter(.config == "Preprocessor1_Model01")
  1. Set up the recipe and the pre-processing steps to build a random forest model. You shouldn’t have to do as many steps. The only step you should need to do is making all integers numeric.
ranger_recipe <- recipe(Class ~ .,
                         data = lending_train) %>% 
  # step_rm(acc_now_delinq, delinq_amnt) %>% 
  step_mutate_at(all_numeric(),
                 fn = ~as.numeric(.))
  # step_mutate(annual_inc =
  #             case_when(annual_inc <= 9875                          ~ 10,
  #                       annual_inc > 9875   && annual_inc <= 40125  ~ 12,
  #                       annual_inc > 40125  && annual_inc <= 85525  ~ 22,
  #                       annual_inc > 85525  && annual_inc <= 163300 ~ 24,
  #                       annual_inc > 163300 && annual_inc <= 207350 ~ 32,
  #                       annual_inc > 207350 && annual_inc <= 518400 ~ 35,
  #                       annual_inc > 518400                         ~ 37)) %>%
  #step_mutate(annual_inc = as.factor(annual_inc)
  1. Set up the random forest model and workflow. We will tune the mtry and min_n parameters and set the number of trees, trees, to 100 (otherwise the next steps take too long).
ranger_spec <- rand_forest(mtry = tune(),
                           min_n = tune(),
                           trees = 100) %>% 
  set_mode("classification") %>% 
  set_engine("ranger")

ranger_spec
## Random Forest Model Specification (classification)
## 
## Main Arguments:
##   mtry = tune()
##   trees = 100
##   min_n = tune()
## 
## Computational engine: ranger
ranger_workflow <- workflow() %>% 
  add_recipe(ranger_recipe) %>% 
  add_model(ranger_spec)

ranger_workflow
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: rand_forest()
## 
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 1 Recipe Step
## 
## ● step_mutate_at()
## 
## ── Model ───────────────────────────────────────────────────────────────────────
## Random Forest Model Specification (classification)
## 
## Main Arguments:
##   mtry = tune()
##   trees = 100
##   min_n = tune()
## 
## Computational engine: ranger
  1. Set up the model tuning for both the mtry and min_n parameters. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Use only 3 levels in the grid. For the mtry parameter, you need to put finalize(mtry(), lending_training %>% select(-Class)) in as an argument instead of just mtry(), where lending_training is the name of your training data. This is because the mtry() grid will otherwise have unknowns in it. This part can take a while to run.
set.seed(494)

lending_rf_grid <- grid_regular(min_n(), 
                                finalize(mtry(), lending_train %>% select(-Class)),
                                levels = 3) 

ctrl_res <- control_stack_grid()

ranger_cv <- ranger_workflow %>% 
  tune_grid(resamples = lending_cv,
            grid = lending_rf_grid,
            control = ctrl_res)
  1. Find the best tuning parameters. What is the are the accuracy and area under the ROC curve for the model with those tuning parameters?
ranger_cv %>% 
  show_best(metric = "accuracy")
ranger_cv %>% 
  collect_metrics() %>% 
  filter(.config == "Preprocessor1_Model4")

The best tuning parameters are 11 for mtry and 2 for min_n.

  1. Use functions from the DALEX and DALEXtra libraries to create a histogram and boxplot of the residuals from the training data. How do they look? Any interesting behavior?
best_param <- lending_lasso_tune %>% 
  select_best(metric = "accuracy")
lending_lasso_final_wf <- lending_workflow %>% 
  finalize_workflow(best_param)
lending_lasso_final_mod <- lending_lasso_final_wf %>% 
  fit(data = lending_train)
best_param_ranger <- ranger_cv %>% 
  select_best(metric = "accuracy")

lending_ranger_final_wf <- ranger_workflow %>% 
  finalize_workflow(best_param_ranger)
set.seed(494)
ranger_fit <- lending_ranger_final_wf %>% 
  fit(lending_train)
lending_lasso_explain <- explain_tidymodels(
  model = lending_lasso_final_mod,
  data = lending_train %>% select(-Class),
  y = as.numeric(lending_train$Class == "good"),
  label = "LASSO",
  type = "classification"
)
## Preparation of a new explainer is initiated
##   -> model label       :  LASSO 
##   -> data              :  9643  rows  22  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  9643  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task classification (  default  ) 
##   -> model_info        :  type set to  classification 
##   -> predicted values  :  numerical, min =  0.02883008 , mean =  0.7276827 , max =  0.9883398  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.9244458 , mean =  -4.632243e-06 , max =  0.9711699  
##   A new explainer has been created! 
lending_rf_explain <- explain_tidymodels(
  model = ranger_fit,
  data = lending_train %>% select(-Class),
  y = as.numeric(lending_train$Class == "good"),
  label = "Random Forest",
  type = "classification"
)
## Preparation of a new explainer is initiated
##   -> model label       :  Random Forest 
##   -> data              :  9643  rows  22  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  9643  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task classification (  default  ) 
##   -> model_info        :  type set to  classification 
##   -> predicted values  :  numerical, min =  0 , mean =  0.7038608 , max =  1  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.4 , mean =  0.02381728 , max =  0.29  
##   A new explainer has been created! 
lending_lasso_model_perf <- model_performance(lending_lasso_explain)
lending_lasso_model_perf
## Measures for:  classification
## recall     : 0.9381502 
## precision  : 0.7804386 
## f1         : 0.852058 
## accuracy   : 0.7629368 
## auc        : 0.7772458
## 
## Residuals:
##          0%         10%         20%         30%         40%         50% 
## -0.92444585 -0.68117176 -0.48463606  0.04695045  0.09244145  0.12651880 
##         60%         70%         80%         90%        100% 
##  0.16297193  0.20979491  0.29607744  0.41138573  0.97116992
lending_rf_model_perf <- model_performance(lending_rf_explain)
lending_rf_model_perf
## Measures for:  classification
## recall     : 1 
## precision  : 1 
## f1         : 1 
## accuracy   : 1 
## auc        : 1
## 
## Residuals:
##    0%   10%   20%   30%   40%   50%   60%   70%   80%   90%  100% 
## -0.40 -0.01  0.00  0.00  0.00  0.01  0.02  0.03  0.05  0.08  0.29
hist_plot <- 
  plot(#lending_lasso_model_perf,
       lending_rf_model_perf, 
       geom = "histogram")
box_plot <-
  plot(#lending_lasso_model_perf,
       lending_rf_model_perf, 
       geom = "boxplot")

hist_plot + box_plot

The histogram is fairly normally distributed with a slight right skew towards positive residuals. The boxplot shows that there is a large amount of residual variance which is evidence by the RMSE being located outside of the IQR, as well as the long tail on the right.

  1. Use DALEX functions to create a variable importance plot from this model. What are the most important variables?
set.seed(494)

lending_lasso_var_imp <- 
  model_parts(
    lending_lasso_explain
    )
lend_lasso_VIP_plot <- 
  plot(lending_lasso_var_imp, show_boxplots = TRUE)

lending_rf_var_imp <- 
  model_parts(
    lending_rf_explain
    )
lend_rf_VIP_plot <- 
  plot(lending_rf_var_imp, show_boxplots = TRUE)

lend_lasso_VIP_plot

lend_rf_VIP_plot

Interest rate seems to be the most important variable by a large margin for the LASSO. For the random forest, annual income and interest rate are also much more important than any of the other variables, with annual income being more important than interest rate, which is the opposite of the LASSO.

  1. Write a function called cp_profile to make a CP profile. The function will take an explainer, a new observation, and a variable name as its arguments and create a CP profile for a quantitative predictor variable. You will need to use the predict_profile() function inside the function you create - put the variable name there so the plotting part is easier. You’ll also want to use aes_string() rather than aes() and quote the variables. Use the cp_profile() function to create one CP profile of your choosing. Be sure to choose a variable that is numeric, not integer. There seem to be issues with those that I’m looking into.
cp_profile <- function(explainer, newObs, varName) {
  predict_profile(explainer = explainer,
                  new_observation = newObs,
                  variables = varName) %>% 
    rename(yhat = `_yhat_`) %>% 
    ggplot(aes_string(x = varName, y = 'yhat')) + 
    geom_point()
}
set.seed(494)

cp_profile(explainer = lending_rf_explain, 
           newObs = lending_train %>% slice_sample(), 
           varName = 'annual_inc')

  1. Use DALEX functions to create partial dependence plots (with the CP profiles in gray) for the 3-4 most important variables. If the important variables are categorical, you can instead make a CP profile for 3 observations in the dataset and discuss how you could go about constructing a partial dependence plot for a categorical variable (you don’t have to code it, but you can if you want an extra challenge). If it ever gives you an error that says, “Error: Can’t convert from VARIABLE to VARIABLE due to loss of precision”, then remove that variable from the list. I seem to have figured out why it’s doing that, but I don’t know how to fix it yet.
rf_pdp <- model_profile(explainer = lending_rf_explain, 
                        variables = c('int_rate', 'annual_inc', 'open_il_12m', 'open_il_24m'))

plot(rf_pdp,
     variables = 'int_rate',
     geom = 'profiles')

plot(rf_pdp,
     variables = 'annual_inc',
     geom = 'profiles')

plot(rf_pdp,
     variables = 'open_il_12m',
     geom = 'profiles')

plot(rf_pdp,
     variables = 'open_il_24m',
     geom = 'profiles')

  1. Fit one more model type of your choosing that will feed into the stacking model.
lending_knn_mod <-
  nearest_neighbor(
    neighbors = tune("k")
  ) %>%
  set_engine("kknn") %>% 
  set_mode("classification")

lending_knn_workflow <- workflow() %>% 
  add_model(lending_knn_mod) %>% 
  add_recipe(lending_recipe)

lending_knn_tune <- lending_knn_workflow %>% 
  tune_grid(resamples = lending_cv, 
            grid = 4,
            control = ctrl_grid)
  1. Create a model stack with the candidate models from the previous parts of the exercise and use the blend_predictions() function to find the coefficients of the stacked model. Create a plot examining the performance metrics for the different penalty parameters to assure you have captured the best one. If not, adjust the penalty. (HINT: use the autoplot() function). Which models are contributing most?
set.seed(494)

lending_stack <-
  stacks() %>% 
  add_candidates(ranger_cv) %>% 
  add_candidates(lending_lasso_tune) %>% 
  add_candidates(lending_knn_tune)
## Warning: Predictions from the candidates c(".pred_bad_lending_lasso_tune_1_02",
## ".pred_bad_lending_lasso_tune_1_03", ".pred_bad_lending_lasso_tune_1_04",
## ".pred_bad_lending_lasso_tune_1_05", ".pred_bad_lending_lasso_tune_1_06",
## ".pred_good_lending_lasso_tune_1_02", ".pred_good_lending_lasso_tune_1_03",
## ".pred_good_lending_lasso_tune_1_04", ".pred_good_lending_lasso_tune_1_05",
## ".pred_good_lending_lasso_tune_1_06") were identical to those from existing
## candidates and were removed from the data stack.
lending_blend <- lending_stack %>% 
  blend_predictions()

lending_blend
## ── A stacked ensemble model ─────────────────────────────────────
## 
## Out of 18 possible candidate members, the ensemble retained 3.
## Lasso penalty: 0.001.
## 
## The 3 highest weighted member classes are:
## # A tibble: 3 x 3
##   member                          type             weight
##   <chr>                           <chr>             <dbl>
## 1 .pred_good_ranger_cv_1_4        rand_forest      12.7  
## 2 .pred_good_lending_knn_tune_1_1 nearest_neighbor  0.930
## 3 .pred_good_ranger_cv_1_7        rand_forest       0.297
## 
## Members have not yet been fitted with `fit_members()`.
autoplot(lending_blend)

autoplot(lending_blend, type = "weights")

The random forest models are contributing the most, with the knn model contributing slightly.

  1. Fit the final stacked model using fit_members(). Apply the model to the test data and report the accuracy and area under the curve. Create a graph of the ROC and construct a confusion matrix. Comment on what you see. Save this final model using the saveRDS() function - see the Use the model section of the tidymodels intro. We are going to use the model in the next part. You’ll want to save it in the folder where you create your shiny app.
lending_final_stack <- lending_blend %>% 
  fit_members()

lending_final_stack
## ── A stacked ensemble model ─────────────────────────────────────
## 
## Out of 18 possible candidate members, the ensemble retained 3.
## Lasso penalty: 0.001.
## 
## The 3 highest weighted member classes are:
## # A tibble: 3 x 3
##   member                          type             weight
##   <chr>                           <chr>             <dbl>
## 1 .pred_good_ranger_cv_1_4        rand_forest      12.7  
## 2 .pred_good_lending_knn_tune_1_1 nearest_neighbor  0.930
## 3 .pred_good_ranger_cv_1_7        rand_forest       0.297
saveRDS(lending_final_stack, file = "lending_final_stack")
lending_stack_test <- lending_test %>% 
  bind_cols(predict(lending_final_stack, new_data = lending_test, type = "prob")) %>% 
  bind_cols(predict(lending_final_stack, new_data = lending_test))

lending_stack_test %>% 
  accuracy(.pred_class, Class)
lending_stack_test %>% 
  roc_auc(Class, .pred_bad)
autoplot(roc_curve(lending_stack_test, Class, .pred_bad))

Shiny App

The shiny app is not finished but a link to the GitHub page is provided to show progress.

  • Here is the GitHub repository for my Shiny app.

Coded Bias

  • The part of the film that impacted me the most was the part where the high school aged kid was stopped on the street in London because the facial recognition technology misidentified him due to his race. It provided me evidence for how much of a problem this truly is. I was most surprised by how widespread facial recognition was used in China, and how China uses it to create a citizen rating. I was also surprised at how the United States does this without nearly as much transparency, which is a huge problem in my eyes. It was truly shocking how transparent China is about facial recognition software uses, and how some citizens are okay with what China is doing. I was also shocked that there is no AI or Machine Learning Algorithm regulation in the United States. Overall, I felt generally unsettled by the documentary, as it made me fear for the future of algorithm usage in the United States.
LS0tCnRpdGxlOiAiQXNzaWdubWVudCAjMiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQoja25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpgYGAKCmBgYHtyIGxpYnJhcmllc30KbGlicmFyeSh0aWR5dmVyc2UpICAgICAgICAgIyBmb3IgZ3JhcGhpbmcgYW5kIGRhdGEgY2xlYW5pbmcKbGlicmFyeSh0aWR5bW9kZWxzKSAgICAgICAgIyBmb3IgbW9kZWxpbmcKbGlicmFyeShzdGFja3MpICAgICAgICAgICAgIyBmb3Igc3RhY2tpbmcgbW9kZWxzCmxpYnJhcnkobmFuaWFyKSAgICAgICAgICAgICMgZm9yIGFuYWx5emluZyBtaXNzaW5nIHZhbHVlcwpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICAgICAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KG1vZGVybmRpdmUpICAgICAgICAjIGZvciBLaW5nIENvdW50eSBob3VzaW5nIGRhdGEKbGlicmFyeSh2aXApICAgICAgICAgICAgICAgIyBmb3IgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cwpsaWJyYXJ5KERBTEVYKSAgICAgICAgICAgICAjIGZvciBtb2RlbCBpbnRlcnByZXRhdGlvbgpsaWJyYXJ5KERBTEVYdHJhKSAgICAgICAgICAjIGZvciBleHRlbnNpb24gb2YgREFMRVgKbGlicmFyeShwYXRjaHdvcmspICAgICAgICAgIyBmb3IgY29tYmluaW5nIHBsb3RzIG5pY2VseQpgYGAKCmBgYHtyIGRhdGF9CmRhdGEoImxlbmRpbmdfY2x1YiIpCmBgYAoKCiMjIFB1dCBpdCBvbiBHaXRIdWIhCgpbSGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2FsZXhkZW56bGVyL1NUQVQ0OTRfc2l0ZV9EZW56bGVyKSBpcyBteSBHaXRIdWIgbGluay4KCiMjIE1vZGVsaW5nCgpXZeKAmWxsIGJlIHVzaW5nIHRoZSBgbGVuZGluZ19jbHViYCBkYXRhc2V0IGZyb20gdGhlIGBtb2RlbGRhdGFgIGxpYnJhcnksIHdoaWNoIGlzIHBhcnQgb2YgYHRpZHltb2RlbHNgLiBUaGUgb3V0Y29tZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBwcmVkaWN0aW5nIGlzIGBDbGFzc2AuIEFuZCBhY2NvcmRpbmcgdG8gdGhlIGRhdGFzZXTigJlzIGhlbHAgcGFnZSwgaXRzIHZhbHVlcyBhcmUg4oCcZWl0aGVyIOKAmGdvb2TigJkgKG1lYW5pbmcgdGhhdCB0aGUgbG9hbiB3YXMgZnVsbHkgcGFpZCBiYWNrIG9yIGN1cnJlbnRseSBvbi10aW1lKSBvciDigJhiYWTigJkgKGNoYXJnZWQgb2ZmLCBkZWZhdWx0ZWQsIG9mIDIxLTEyMCBkYXlzIGxhdGUp4oCdLiAKCiMjIyAqKlRhc2tzKioKCihAKSBFeHBsb3JlIHRoZSBkYXRhLCBjb25jZW50cmF0aW5nIG9uIGV4YW1pbmluZyBkaXN0cmlidXRpb25zIG9mIHZhcmlhYmxlcyBhbmQgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzLgoKYGBge3J9CmxlbmRpbmdfY2x1YiAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZnVuZGVkX2FtbnQpKSArCiAgZ2VvbV9kZW5zaXR5KCkgKyAKICBmYWNldF93cmFwKHZhcnMoQ2xhc3MpKQpgYGAKCmBgYHtyfQpsZW5kaW5nX2NsdWIgJT4lIAogIGdncGxvdChhZXMoeCA9IGludF9yYXRlKSkgKwogIGdlb21fZGVuc2l0eSgpICsgCiAgZmFjZXRfd3JhcCh2YXJzKENsYXNzKSkKYGBgCgpgYGB7cn0KbGVuZGluZ19jbHViICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhbm51YWxfaW5jKSkgKwogIGdlb21fZGVuc2l0eSgpICsgCiAgZmFjZXRfd3JhcCh2YXJzKENsYXNzKSkKYGBgCgoKYGBge3J9CmxlbmRpbmdfY2x1YiAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gYWRkcl9zdGF0ZSwgZmlsbCA9IENsYXNzKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKQpgYGAKCmBgYHtyfQpsZW5kaW5nX2NsdWIgJT4lIAogIGNvdW50KENsYXNzKQpgYGAKYGBge3J9CmxlbmRpbmdfY2x1YiAlPiUgCiAgZ3JvdXBfYnkoYWRkcl9zdGF0ZSkgJT4lIAogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lIAogIGFycmFuZ2UoZGVzYyhjb3VudCkpCmBgYAoKCgooQCkgRG8gYW55IGRhdGEgY2xlYW5pbmcgc3RlcHMgdGhhdCBuZWVkIHRvIGhhcHBlbiBiZWZvcmUgdGhlIG1vZGVsIGlzIGJ1aWxkLiBGb3IgZXhhbXBsZSwgeW91IG1pZ2h0IHJlbW92ZSBhbnkgdmFyaWFibGVzIHRoYXQgbWVhbiB0aGUgc2FtZSB0aGluZyBhcyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgKG5vdCBzdXJlIGlmIHRoYXQgaGFwcGVucyBoZXJlKSwgZ2V0IHJpZCBvZiByb3dzIHdoZXJlIGFsbCB2YXJpYWJsZXMgaGF2ZSBtaXNzaW5nIHZhbHVlcywgZXRjLgoKYGBge3J9CmNyZWF0ZV9tb3JlX2JhZCA8LSBsZW5kaW5nX2NsdWIgJT4lIAogIGZpbHRlcihDbGFzcyA9PSAiYmFkIikgJT4lIAogIHNhbXBsZV9uKHNpemUgPSAzMDAwLCByZXBsYWNlID0gVFJVRSkKCmxlbmRpbmdfY2x1Yl9tb2QgPC0gbGVuZGluZ19jbHViICU+JSAKICBiaW5kX3Jvd3MoY3JlYXRlX21vcmVfYmFkKQpgYGAKCgooQCkgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdCwgcHV0dGluZyA3NSUgaW4gdGhlIHRyYWluaW5nIGRhdGEuCgpgYGB7cn0Kc2V0LnNlZWQoNDk0KQoKbGVuZGluZ19zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGxlbmRpbmdfY2x1Yl9tb2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IDAuNzUpCmxlbmRpbmdfdHJhaW4gPC0gdHJhaW5pbmcobGVuZGluZ19zcGxpdCkKbGVuZGluZ190ZXN0ICA8LSB0ZXN0aW5nKGxlbmRpbmdfc3BsaXQpCmBgYAoKCihAKSBTZXQgdXAgdGhlIHJlY2lwZSBhbmQgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIHRvIGJ1aWxkIGEgbGFzc28gbW9kZWwuIFNvbWUgc3RlcHMgeW91IHNob3VsZCB0YWtlOgoKKiBNYWtlIGFsbCBpbnRlZ2VyIHZhcmlhYmxlcyBudW1lcmljIChJ4oCZZCBoaWdobHkgcmVjb21tZW5kIHVzaW5nIGBzdGVwX211dGF0ZV9hdCgpYCBvciB0aGlzIHdpbGwgYmUgYSBsb3Qgb2YgY29kZSkuIFdl4oCZbGwgd2FudCB0byBkbyB0aGlzIGZvciB0aGUgbW9kZWwgaW50ZXJwcmV0YXRpb24gd2XigJlsbCBkbyBsYXRlci4KKiBUaGluayBhYm91dCBncm91cGluZyBmYWN0b3IgdmFyaWFibGVzIHdpdGggbWFueSBsZXZlbHMuCiogTWFrZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgZHVtbXkgdmFyaWFibGVzIChtYWtlIHN1cmUgTk9UIHRvIGRvIHRoaXMgdG8gdGhlIG91dGNvbWUgdmFyaWFibGUpLgoqIE5vcm1hbGl6ZSBxdWFudGl0YXRpdmUgdmFyaWFibGVzLgoKCmBgYHtyfQpsZW5kaW5nX3JlY2lwZSA8LSByZWNpcGUoQ2xhc3MgfiAuLAogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGxlbmRpbmdfdHJhaW4pICU+JSAKICBzdGVwX3JtKGFjY19ub3dfZGVsaW5xLCBkZWxpbnFfYW1udCkgJT4lIAogIHN0ZXBfbXV0YXRlX2F0KGFsbF9udW1lcmljKCksCiAgICAgICAgICAgICAgICAgZm4gPSB+YXMubnVtZXJpYyguKSkgJT4lIAogICMgc3RlcF9tdXRhdGUoYW5udWFsX2luYyA9CiAgIyAgICAgICAgICAgICBjYXNlX3doZW4oYW5udWFsX2luYyA8PSA5ODc1ICAgICAgICAgICAgICAgICAgICAgICAgICB+IDEwLAogICMgICAgICAgICAgICAgICAgICAgICAgIGFubnVhbF9pbmMgPiA5ODc1ICAgJiYgYW5udWFsX2luYyA8PSA0MDEyNSAgfiAxMiwKICAjICAgICAgICAgICAgICAgICAgICAgICBhbm51YWxfaW5jID4gNDAxMjUgICYmIGFubnVhbF9pbmMgPD0gODU1MjUgIH4gMjIsCiAgIyAgICAgICAgICAgICAgICAgICAgICAgYW5udWFsX2luYyA+IDg1NTI1ICAmJiBhbm51YWxfaW5jIDw9IDE2MzMwMCB+IDI0LAogICMgICAgICAgICAgICAgICAgICAgICAgIGFubnVhbF9pbmMgPiAxNjMzMDAgJiYgYW5udWFsX2luYyA8PSAyMDczNTAgfiAzMiwKICAjICAgICAgICAgICAgICAgICAgICAgICBhbm51YWxfaW5jID4gMjA3MzUwICYmIGFubnVhbF9pbmMgPD0gNTE4NDAwIH4gMzUsCiAgIyAgICAgICAgICAgICAgICAgICAgICAgYW5udWFsX2luYyA+IDUxODQwMCAgICAgICAgICAgICAgICAgICAgICAgICB+IDM3KSkgJT4lCiAgIyBzdGVwX211dGF0ZShhbm51YWxfaW5jID0gYXMuZmFjdG9yKGFubnVhbF9pbmMpKSAlPiUgCiAgc3RlcF9ub3JtYWxpemUoYWxsX3ByZWRpY3RvcnMoKSwgLWFsbF9ub21pbmFsKCkpICU+JSAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSkKCmxlbmRpbmdfcmVjaXBlICU+JSAKICBwcmVwKGxlbmRpbmdfdHJhaW4pICU+JSAKICBqdWljZSgpCmBgYAoKKEApIFNldCB1cCB0aGUgTEFTU08gbW9kZWwgYW5kIHdvcmtmbG93LiBXZSB3aWxsIHR1bmUgdGhlIGBwZW5hbHR5YCBwYXJhbWV0ZXIuCgpgYGB7cn0KbGFzc29fbGVuZGluZ19tb2QgPC0gbG9naXN0aWNfcmVnKG1peHR1cmUgPSAxKSAlPiUKICBzZXRfZW5naW5lKCJnbG1uZXQiKSAlPiUgCiAgICBzZXRfYXJncyhwZW5hbHR5ID0gdHVuZSgpKSAlPiUgCiAgICAgIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgpsYXNzb19sZW5kaW5nX21vZAoKbGVuZGluZ193b3JrZmxvdyA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9yZWNpcGUobGVuZGluZ19yZWNpcGUpICU+JQogICAgYWRkX21vZGVsKGxhc3NvX2xlbmRpbmdfbW9kKQoKbGVuZGluZ193b3JrZmxvdwpgYGAKCgooQCkgU2V0IHVwIHRoZSBtb2RlbCB0dW5pbmcgZm9yIHRoZSBgcGVuYWx0eWAgcGFyYW1ldGVyLiBCZSBzdXJlIHRvIGFkZCB0aGUgYGNvbnRyb2xfc3RhY2tfZ3JpZCgpYCBmb3IgdGhlIGBjb250cm9sYCBhcmd1bWVudCBzbyB3ZSBjYW4gdXNlIHRoZXNlIHJlc3VsdHMgbGF0ZXIgd2hlbiB3ZSBzdGFjay4gRmluZCB0aGUgYWNjdXJhY3kgYW5kIGFyZWEgdW5kZXIgdGhlIHJvYyBjdXJ2ZSBmb3IgdGhlIG1vZGVsIHdpdGggdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlci4gVXNlIDUtZm9sZCBjdi4KCmBgYHtyfQpzZXQuc2VlZCg0OTQpCgpsZW5kaW5nX2N2IDwtIHZmb2xkX2N2KGxlbmRpbmdfdHJhaW4sIHYgPSA1KQoKbGVuZGluZ19sYXNzb19wZW5fZ3JpZCA8LSBncmlkX3JlZ3VsYXIocGVuYWx0eSgpLCBsZXZlbHMgPSAxMCkKCmN0cmxfZ3JpZCA8LSBjb250cm9sX3N0YWNrX2dyaWQoKQoKbGVuZGluZ19sYXNzb190dW5lIDwtIGxlbmRpbmdfd29ya2Zsb3cgJT4lIAogIHR1bmVfZ3JpZChyZXNhbXBsZXMgPSBsZW5kaW5nX2N2LAogICAgICAgICAgICBncmlkID0gbGVuZGluZ19sYXNzb19wZW5fZ3JpZCwKICAgICAgICAgICAgY29udHJvbCA9IGN0cmxfZ3JpZCkKYGBgCgpgYGB7cn0KbGVuZGluZ19sYXNzb190dW5lICU+JSAKICBzaG93X2Jlc3QobWV0cmljID0gImFjY3VyYWN5IikKYGBgCgpgYGB7cn0KbGVuZGluZ19sYXNzb190dW5lICU+JSAKICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgCiAgZmlsdGVyKC5jb25maWcgPT0gIlByZXByb2Nlc3NvcjFfTW9kZWwwMSIpCmBgYAoKCihAKSBTZXQgdXAgdGhlIHJlY2lwZSBhbmQgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIHRvIGJ1aWxkIGEgcmFuZG9tIGZvcmVzdCBtb2RlbC4gWW91IHNob3VsZG7igJl0IGhhdmUgdG8gZG8gYXMgbWFueSBzdGVwcy4gVGhlIG9ubHkgc3RlcCB5b3Ugc2hvdWxkIG5lZWQgdG8gZG8gaXMgbWFraW5nIGFsbCBpbnRlZ2VycyBudW1lcmljLgoKYGBge3J9CnJhbmdlcl9yZWNpcGUgPC0gcmVjaXBlKENsYXNzIH4gLiwKICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBsZW5kaW5nX3RyYWluKSAlPiUgCiAgIyBzdGVwX3JtKGFjY19ub3dfZGVsaW5xLCBkZWxpbnFfYW1udCkgJT4lIAogIHN0ZXBfbXV0YXRlX2F0KGFsbF9udW1lcmljKCksCiAgICAgICAgICAgICAgICAgZm4gPSB+YXMubnVtZXJpYyguKSkKICAjIHN0ZXBfbXV0YXRlKGFubnVhbF9pbmMgPQogICMgICAgICAgICAgICAgY2FzZV93aGVuKGFubnVhbF9pbmMgPD0gOTg3NSAgICAgICAgICAgICAgICAgICAgICAgICAgfiAxMCwKICAjICAgICAgICAgICAgICAgICAgICAgICBhbm51YWxfaW5jID4gOTg3NSAgICYmIGFubnVhbF9pbmMgPD0gNDAxMjUgIH4gMTIsCiAgIyAgICAgICAgICAgICAgICAgICAgICAgYW5udWFsX2luYyA+IDQwMTI1ICAmJiBhbm51YWxfaW5jIDw9IDg1NTI1ICB+IDIyLAogICMgICAgICAgICAgICAgICAgICAgICAgIGFubnVhbF9pbmMgPiA4NTUyNSAgJiYgYW5udWFsX2luYyA8PSAxNjMzMDAgfiAyNCwKICAjICAgICAgICAgICAgICAgICAgICAgICBhbm51YWxfaW5jID4gMTYzMzAwICYmIGFubnVhbF9pbmMgPD0gMjA3MzUwIH4gMzIsCiAgIyAgICAgICAgICAgICAgICAgICAgICAgYW5udWFsX2luYyA+IDIwNzM1MCAmJiBhbm51YWxfaW5jIDw9IDUxODQwMCB+IDM1LAogICMgICAgICAgICAgICAgICAgICAgICAgIGFubnVhbF9pbmMgPiA1MTg0MDAgICAgICAgICAgICAgICAgICAgICAgICAgfiAzNykpICU+JQogICNzdGVwX211dGF0ZShhbm51YWxfaW5jID0gYXMuZmFjdG9yKGFubnVhbF9pbmMpCmBgYAoKKEApIFNldCB1cCB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBhbmQgd29ya2Zsb3cuIFdlIHdpbGwgdHVuZSB0aGUgYG10cnlgIGFuZCBgbWluX25gIHBhcmFtZXRlcnMgYW5kIHNldCB0aGUgbnVtYmVyIG9mIHRyZWVzLCBgdHJlZXNgLCB0byAxMDAgKG90aGVyd2lzZSB0aGUgbmV4dCBzdGVwcyB0YWtlIHRvbyBsb25nKS4KCmBgYHtyfQpyYW5nZXJfc3BlYyA8LSByYW5kX2ZvcmVzdChtdHJ5ID0gdHVuZSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbiA9IHR1bmUoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJlZXMgPSAxMDApICU+JSAKICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCnJhbmdlcl9zcGVjCgpyYW5nZXJfd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUgCiAgYWRkX3JlY2lwZShyYW5nZXJfcmVjaXBlKSAlPiUgCiAgYWRkX21vZGVsKHJhbmdlcl9zcGVjKQoKcmFuZ2VyX3dvcmtmbG93CmBgYAoKKEApIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciBib3RoIHRoZSBgbXRyeWAgYW5kIGBtaW5fbmAgcGFyYW1ldGVycy4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBjb250cm9sIGFyZ3VtZW50IHNvIHdlIGNhbiB1c2UgdGhlc2UgcmVzdWx0cyBsYXRlciB3aGVuIHdlIHN0YWNrLiBVc2Ugb25seSAzIGxldmVscyBpbiB0aGUgZ3JpZC4gRm9yIHRoZSBgbXRyeWAgcGFyYW1ldGVyLCB5b3UgbmVlZCB0byBwdXQgYGZpbmFsaXplKG10cnkoKSwgbGVuZGluZ190cmFpbmluZyAlPiUgc2VsZWN0KC1DbGFzcykpYCBpbiBhcyBhbiBhcmd1bWVudCBpbnN0ZWFkIG9mIGp1c3QgYG10cnkoKWAsIHdoZXJlIGBsZW5kaW5nX3RyYWluaW5nYCBpcyB0aGUgbmFtZSBvZiB5b3VyIHRyYWluaW5nIGRhdGEuIFRoaXMgaXMgYmVjYXVzZSB0aGUgYG10cnkoKWAgZ3JpZCB3aWxsIG90aGVyd2lzZSBoYXZlIHVua25vd25zIGluIGl0LiBUaGlzIHBhcnQgY2FuIHRha2UgYSB3aGlsZSB0byBydW4uCgpgYGB7cn0Kc2V0LnNlZWQoNDk0KQoKbGVuZGluZ19yZl9ncmlkIDwtIGdyaWRfcmVndWxhcihtaW5fbigpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaW5hbGl6ZShtdHJ5KCksIGxlbmRpbmdfdHJhaW4gJT4lIHNlbGVjdCgtQ2xhc3MpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAzKSAKCmN0cmxfcmVzIDwtIGNvbnRyb2xfc3RhY2tfZ3JpZCgpCgpyYW5nZXJfY3YgPC0gcmFuZ2VyX3dvcmtmbG93ICU+JSAKICB0dW5lX2dyaWQocmVzYW1wbGVzID0gbGVuZGluZ19jdiwKICAgICAgICAgICAgZ3JpZCA9IGxlbmRpbmdfcmZfZ3JpZCwKICAgICAgICAgICAgY29udHJvbCA9IGN0cmxfcmVzKQpgYGAKCgooQCkgRmluZCB0aGUgYmVzdCB0dW5pbmcgcGFyYW1ldGVycy4gV2hhdCBpcyB0aGUgYXJlIHRoZSBhY2N1cmFjeSBhbmQgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlIGZvciB0aGUgbW9kZWwgd2l0aCB0aG9zZSB0dW5pbmcgcGFyYW1ldGVycz8KCmBgYHtyfQpyYW5nZXJfY3YgJT4lIAogIHNob3dfYmVzdChtZXRyaWMgPSAiYWNjdXJhY3kiKQpgYGAKCmBgYHtyfQpyYW5nZXJfY3YgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpICU+JSAKICBmaWx0ZXIoLmNvbmZpZyA9PSAiUHJlcHJvY2Vzc29yMV9Nb2RlbDQiKQpgYGAKClRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXJzIGFyZSAxMSBmb3IgYG10cnlgIGFuZCAyIGZvciBgbWluX25gLgoKCihAKSBVc2UgZnVuY3Rpb25zIGZyb20gdGhlIGBEQUxFWGAgYW5kIGBEQUxFWHRyYWAgbGlicmFyaWVzIHRvIGNyZWF0ZSBhIGhpc3RvZ3JhbSBhbmQgYm94cGxvdCBvZiB0aGUgcmVzaWR1YWxzIGZyb20gdGhlIHRyYWluaW5nIGRhdGEuIEhvdyBkbyB0aGV5IGxvb2s/IEFueSBpbnRlcmVzdGluZyBiZWhhdmlvcj8KCmBgYHtyfQpiZXN0X3BhcmFtIDwtIGxlbmRpbmdfbGFzc29fdHVuZSAlPiUgCiAgc2VsZWN0X2Jlc3QobWV0cmljID0gImFjY3VyYWN5IikKbGVuZGluZ19sYXNzb19maW5hbF93ZiA8LSBsZW5kaW5nX3dvcmtmbG93ICU+JSAKICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3BhcmFtKQpsZW5kaW5nX2xhc3NvX2ZpbmFsX21vZCA8LSBsZW5kaW5nX2xhc3NvX2ZpbmFsX3dmICU+JSAKICBmaXQoZGF0YSA9IGxlbmRpbmdfdHJhaW4pCmBgYAoKYGBge3J9CmJlc3RfcGFyYW1fcmFuZ2VyIDwtIHJhbmdlcl9jdiAlPiUgCiAgc2VsZWN0X2Jlc3QobWV0cmljID0gImFjY3VyYWN5IikKCmxlbmRpbmdfcmFuZ2VyX2ZpbmFsX3dmIDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9wYXJhbV9yYW5nZXIpCmBgYAoKYGBge3J9CnNldC5zZWVkKDQ5NCkKcmFuZ2VyX2ZpdCA8LSBsZW5kaW5nX3Jhbmdlcl9maW5hbF93ZiAlPiUgCiAgZml0KGxlbmRpbmdfdHJhaW4pCmBgYAoKYGBge3J9CmxlbmRpbmdfbGFzc29fZXhwbGFpbiA8LSBleHBsYWluX3RpZHltb2RlbHMoCiAgbW9kZWwgPSBsZW5kaW5nX2xhc3NvX2ZpbmFsX21vZCwKICBkYXRhID0gbGVuZGluZ190cmFpbiAlPiUgc2VsZWN0KC1DbGFzcyksCiAgeSA9IGFzLm51bWVyaWMobGVuZGluZ190cmFpbiRDbGFzcyA9PSAiZ29vZCIpLAogIGxhYmVsID0gIkxBU1NPIiwKICB0eXBlID0gImNsYXNzaWZpY2F0aW9uIgopCmBgYAoKYGBge3J9CmxlbmRpbmdfcmZfZXhwbGFpbiA8LSBleHBsYWluX3RpZHltb2RlbHMoCiAgbW9kZWwgPSByYW5nZXJfZml0LAogIGRhdGEgPSBsZW5kaW5nX3RyYWluICU+JSBzZWxlY3QoLUNsYXNzKSwKICB5ID0gYXMubnVtZXJpYyhsZW5kaW5nX3RyYWluJENsYXNzID09ICJnb29kIiksCiAgbGFiZWwgPSAiUmFuZG9tIEZvcmVzdCIsCiAgdHlwZSA9ICJjbGFzc2lmaWNhdGlvbiIKKQpgYGAKCmBgYHtyfQpsZW5kaW5nX2xhc3NvX21vZGVsX3BlcmYgPC0gbW9kZWxfcGVyZm9ybWFuY2UobGVuZGluZ19sYXNzb19leHBsYWluKQpsZW5kaW5nX2xhc3NvX21vZGVsX3BlcmYKCmxlbmRpbmdfcmZfbW9kZWxfcGVyZiA8LSBtb2RlbF9wZXJmb3JtYW5jZShsZW5kaW5nX3JmX2V4cGxhaW4pCmxlbmRpbmdfcmZfbW9kZWxfcGVyZgpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9OH0KaGlzdF9wbG90IDwtIAogIHBsb3QoI2xlbmRpbmdfbGFzc29fbW9kZWxfcGVyZiwKICAgICAgIGxlbmRpbmdfcmZfbW9kZWxfcGVyZiwgCiAgICAgICBnZW9tID0gImhpc3RvZ3JhbSIpCmJveF9wbG90IDwtCiAgcGxvdCgjbGVuZGluZ19sYXNzb19tb2RlbF9wZXJmLAogICAgICAgbGVuZGluZ19yZl9tb2RlbF9wZXJmLCAKICAgICAgIGdlb20gPSAiYm94cGxvdCIpCgpoaXN0X3Bsb3QgKyBib3hfcGxvdApgYGAKClRoZSBoaXN0b2dyYW0gaXMgZmFpcmx5IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHdpdGggYSBzbGlnaHQgcmlnaHQgc2tldyB0b3dhcmRzIHBvc2l0aXZlIHJlc2lkdWFscy4gVGhlIGJveHBsb3Qgc2hvd3MgdGhhdCB0aGVyZSBpcyBhIGxhcmdlIGFtb3VudCBvZiByZXNpZHVhbCB2YXJpYW5jZSB3aGljaCBpcyBldmlkZW5jZSBieSB0aGUgUk1TRSBiZWluZyBsb2NhdGVkIG91dHNpZGUgb2YgdGhlIElRUiwgYXMgd2VsbCBhcyB0aGUgbG9uZyB0YWlsIG9uIHRoZSByaWdodC4gIAoKCihAKSBVc2UgYERBTEVYYCBmdW5jdGlvbnMgdG8gY3JlYXRlIGEgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90IGZyb20gdGhpcyBtb2RlbC4gV2hhdCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcz8KCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9OH0Kc2V0LnNlZWQoNDk0KQoKbGVuZGluZ19sYXNzb192YXJfaW1wIDwtIAogIG1vZGVsX3BhcnRzKAogICAgbGVuZGluZ19sYXNzb19leHBsYWluCiAgICApCmxlbmRfbGFzc29fVklQX3Bsb3QgPC0gCiAgcGxvdChsZW5kaW5nX2xhc3NvX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQoKbGVuZGluZ19yZl92YXJfaW1wIDwtIAogIG1vZGVsX3BhcnRzKAogICAgbGVuZGluZ19yZl9leHBsYWluCiAgICApCmxlbmRfcmZfVklQX3Bsb3QgPC0gCiAgcGxvdChsZW5kaW5nX3JmX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQoKbGVuZF9sYXNzb19WSVBfcGxvdApsZW5kX3JmX1ZJUF9wbG90CmBgYAoKSW50ZXJlc3QgcmF0ZSBzZWVtcyB0byBiZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgYnkgYSBsYXJnZSBtYXJnaW4gZm9yIHRoZSBMQVNTTy4gRm9yIHRoZSByYW5kb20gZm9yZXN0LCBhbm51YWwgaW5jb21lIGFuZCBpbnRlcmVzdCByYXRlIGFyZSBhbHNvIG11Y2ggbW9yZSBpbXBvcnRhbnQgdGhhbiBhbnkgb2YgdGhlIG90aGVyIHZhcmlhYmxlcywgd2l0aCBhbm51YWwgaW5jb21lIGJlaW5nIG1vcmUgaW1wb3J0YW50IHRoYW4gaW50ZXJlc3QgcmF0ZSwgd2hpY2ggaXMgdGhlIG9wcG9zaXRlIG9mIHRoZSBMQVNTTy4KCihAKSBXcml0ZSBhIGZ1bmN0aW9uIGNhbGxlZCBgY3BfcHJvZmlsZWAgdG8gbWFrZSBhIENQIHByb2ZpbGUuIFRoZSBmdW5jdGlvbiB3aWxsIHRha2UgYW4gZXhwbGFpbmVyLCBhIG5ldyBvYnNlcnZhdGlvbiwgYW5kIGEgdmFyaWFibGUgbmFtZSBhcyBpdHMgYXJndW1lbnRzIGFuZCBjcmVhdGUgYSBDUCBwcm9maWxlIGZvciBhIHF1YW50aXRhdGl2ZSBwcmVkaWN0b3IgdmFyaWFibGUuIFlvdSB3aWxsIG5lZWQgdG8gdXNlIHRoZSBgcHJlZGljdF9wcm9maWxlKClgIGZ1bmN0aW9uIGluc2lkZSB0aGUgZnVuY3Rpb24geW91IGNyZWF0ZSAtIHB1dCB0aGUgdmFyaWFibGUgbmFtZSB0aGVyZSBzbyB0aGUgcGxvdHRpbmcgcGFydCBpcyBlYXNpZXIuIFlvdeKAmWxsIGFsc28gd2FudCB0byB1c2UgYGFlc19zdHJpbmcoKWAgcmF0aGVyIHRoYW4gYGFlcygpYCBhbmQgcXVvdGUgdGhlIHZhcmlhYmxlcy4gVXNlIHRoZSBgY3BfcHJvZmlsZSgpYCBmdW5jdGlvbiB0byBjcmVhdGUgb25lIENQIHByb2ZpbGUgb2YgeW91ciBjaG9vc2luZy4gQmUgc3VyZSB0byBjaG9vc2UgYSB2YXJpYWJsZSB0aGF0IGlzIG51bWVyaWMsIG5vdCBpbnRlZ2VyLiBUaGVyZSBzZWVtIHRvIGJlIGlzc3VlcyB3aXRoIHRob3NlIHRoYXQgSeKAmW0gbG9va2luZyBpbnRvLgoKYGBge3J9CmNwX3Byb2ZpbGUgPC0gZnVuY3Rpb24oZXhwbGFpbmVyLCBuZXdPYnMsIHZhck5hbWUpIHsKICBwcmVkaWN0X3Byb2ZpbGUoZXhwbGFpbmVyID0gZXhwbGFpbmVyLAogICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdPYnMsCiAgICAgICAgICAgICAgICAgIHZhcmlhYmxlcyA9IHZhck5hbWUpICU+JSAKICAgIHJlbmFtZSh5aGF0ID0gYF95aGF0X2ApICU+JSAKICAgIGdncGxvdChhZXNfc3RyaW5nKHggPSB2YXJOYW1lLCB5ID0gJ3loYXQnKSkgKyAKICAgIGdlb21fcG9pbnQoKQp9CmBgYAoKYGBge3J9CnNldC5zZWVkKDQ5NCkKCmNwX3Byb2ZpbGUoZXhwbGFpbmVyID0gbGVuZGluZ19yZl9leHBsYWluLCAKICAgICAgICAgICBuZXdPYnMgPSBsZW5kaW5nX3RyYWluICU+JSBzbGljZV9zYW1wbGUoKSwgCiAgICAgICAgICAgdmFyTmFtZSA9ICdhbm51YWxfaW5jJykKYGBgCgooQCkgVXNlIGBEQUxFWGAgZnVuY3Rpb25zIHRvIGNyZWF0ZSBwYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdHMgKHdpdGggdGhlIENQIHByb2ZpbGVzIGluIGdyYXkpIGZvciB0aGUgMy00IG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcy4gSWYgdGhlIGltcG9ydGFudCB2YXJpYWJsZXMgYXJlIGNhdGVnb3JpY2FsLCB5b3UgY2FuIGluc3RlYWQgbWFrZSBhIENQIHByb2ZpbGUgZm9yIDMgb2JzZXJ2YXRpb25zIGluIHRoZSBkYXRhc2V0IGFuZCBkaXNjdXNzIGhvdyB5b3UgY291bGQgZ28gYWJvdXQgY29uc3RydWN0aW5nIGEgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3QgZm9yIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgKHlvdSBkb27igJl0IGhhdmUgdG8gY29kZSBpdCwgYnV0IHlvdSBjYW4gaWYgeW91IHdhbnQgYW4gZXh0cmEgY2hhbGxlbmdlKS4gSWYgaXQgZXZlciBnaXZlcyB5b3UgYW4gZXJyb3IgdGhhdCBzYXlzLCDigJxFcnJvcjogQ2Fu4oCZdCBjb252ZXJ0IGZyb20gYFZBUklBQkxFYCB0byBgVkFSSUFCTEVgIGR1ZSB0byBsb3NzIG9mIHByZWNpc2lvbuKAnSwgdGhlbiByZW1vdmUgdGhhdCB2YXJpYWJsZSBmcm9tIHRoZSBsaXN0LiBJIHNlZW0gdG8gaGF2ZSBmaWd1cmVkIG91dCB3aHkgaXTigJlzIGRvaW5nIHRoYXQsIGJ1dCBJIGRvbuKAmXQga25vdyBob3cgdG8gZml4IGl0IHlldC4KCmBgYHtyfQpyZl9wZHAgPC0gbW9kZWxfcHJvZmlsZShleHBsYWluZXIgPSBsZW5kaW5nX3JmX2V4cGxhaW4sIAogICAgICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZXMgPSBjKCdpbnRfcmF0ZScsICdhbm51YWxfaW5jJywgJ29wZW5faWxfMTJtJywgJ29wZW5faWxfMjRtJykpCgpwbG90KHJmX3BkcCwKICAgICB2YXJpYWJsZXMgPSAnaW50X3JhdGUnLAogICAgIGdlb20gPSAncHJvZmlsZXMnKQoKcGxvdChyZl9wZHAsCiAgICAgdmFyaWFibGVzID0gJ2FubnVhbF9pbmMnLAogICAgIGdlb20gPSAncHJvZmlsZXMnKQoKcGxvdChyZl9wZHAsCiAgICAgdmFyaWFibGVzID0gJ29wZW5faWxfMTJtJywKICAgICBnZW9tID0gJ3Byb2ZpbGVzJykKCnBsb3QocmZfcGRwLAogICAgIHZhcmlhYmxlcyA9ICdvcGVuX2lsXzI0bScsCiAgICAgZ2VvbSA9ICdwcm9maWxlcycpCmBgYAoKCihAKSBGaXQgb25lIG1vcmUgbW9kZWwgdHlwZSBvZiB5b3VyIGNob29zaW5nIHRoYXQgd2lsbCBmZWVkIGludG8gdGhlIHN0YWNraW5nIG1vZGVsLgoKYGBge3J9CmxlbmRpbmdfa25uX21vZCA8LQogIG5lYXJlc3RfbmVpZ2hib3IoCiAgICBuZWlnaGJvcnMgPSB0dW5lKCJrIikKICApICU+JQogIHNldF9lbmdpbmUoImtrbm4iKSAlPiUgCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKCmxlbmRpbmdfa25uX3dvcmtmbG93IDwtIHdvcmtmbG93KCkgJT4lIAogIGFkZF9tb2RlbChsZW5kaW5nX2tubl9tb2QpICU+JSAKICBhZGRfcmVjaXBlKGxlbmRpbmdfcmVjaXBlKQoKbGVuZGluZ19rbm5fdHVuZSA8LSBsZW5kaW5nX2tubl93b3JrZmxvdyAlPiUgCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGxlbmRpbmdfY3YsIAogICAgICAgICAgICBncmlkID0gNCwKICAgICAgICAgICAgY29udHJvbCA9IGN0cmxfZ3JpZCkKYGBgCgoKKEApIENyZWF0ZSBhIG1vZGVsIHN0YWNrIHdpdGggdGhlIGNhbmRpZGF0ZSBtb2RlbHMgZnJvbSB0aGUgcHJldmlvdXMgcGFydHMgb2YgdGhlIGV4ZXJjaXNlIGFuZCB1c2UgdGhlIGBibGVuZF9wcmVkaWN0aW9ucygpYCBmdW5jdGlvbiB0byBmaW5kIHRoZSBjb2VmZmljaWVudHMgb2YgdGhlIHN0YWNrZWQgbW9kZWwuIENyZWF0ZSBhIHBsb3QgZXhhbWluaW5nIHRoZSBwZXJmb3JtYW5jZSBtZXRyaWNzIGZvciB0aGUgZGlmZmVyZW50IHBlbmFsdHkgcGFyYW1ldGVycyB0byBhc3N1cmUgeW91IGhhdmUgY2FwdHVyZWQgdGhlIGJlc3Qgb25lLiBJZiBub3QsIGFkanVzdCB0aGUgcGVuYWx0eS4gKEhJTlQ6IHVzZSB0aGUgYGF1dG9wbG90KClgIGZ1bmN0aW9uKS4gV2hpY2ggbW9kZWxzIGFyZSBjb250cmlidXRpbmcgbW9zdD8KCmBgYHtyfQpzZXQuc2VlZCg0OTQpCgpsZW5kaW5nX3N0YWNrIDwtCiAgc3RhY2tzKCkgJT4lIAogIGFkZF9jYW5kaWRhdGVzKHJhbmdlcl9jdikgJT4lIAogIGFkZF9jYW5kaWRhdGVzKGxlbmRpbmdfbGFzc29fdHVuZSkgJT4lIAogIGFkZF9jYW5kaWRhdGVzKGxlbmRpbmdfa25uX3R1bmUpCgpsZW5kaW5nX2JsZW5kIDwtIGxlbmRpbmdfc3RhY2sgJT4lIAogIGJsZW5kX3ByZWRpY3Rpb25zKCkKCmxlbmRpbmdfYmxlbmQKCmF1dG9wbG90KGxlbmRpbmdfYmxlbmQpCmF1dG9wbG90KGxlbmRpbmdfYmxlbmQsIHR5cGUgPSAid2VpZ2h0cyIpCmBgYAoKVGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWxzIGFyZSBjb250cmlidXRpbmcgdGhlIG1vc3QsIHdpdGggdGhlIGtubiBtb2RlbCBjb250cmlidXRpbmcgc2xpZ2h0bHkuCgooQCkgRml0IHRoZSBmaW5hbCBzdGFja2VkIG1vZGVsIHVzaW5nIGBmaXRfbWVtYmVycygpYC4gQXBwbHkgdGhlIG1vZGVsIHRvIHRoZSB0ZXN0IGRhdGEgYW5kIHJlcG9ydCB0aGUgYWNjdXJhY3kgYW5kIGFyZWEgdW5kZXIgdGhlIGN1cnZlLiBDcmVhdGUgYSBncmFwaCBvZiB0aGUgUk9DIGFuZCBjb25zdHJ1Y3QgYSBjb25mdXNpb24gbWF0cml4LiBDb21tZW50IG9uIHdoYXQgeW91IHNlZS4gU2F2ZSB0aGlzIGZpbmFsIG1vZGVsIHVzaW5nIHRoZSBgc2F2ZVJEUygpYCBmdW5jdGlvbiAtIHNlZSB0aGUgVXNlIHRoZSBtb2RlbCBzZWN0aW9uIG9mIHRoZSB0aWR5bW9kZWxzIGludHJvLiBXZSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBtb2RlbCBpbiB0aGUgbmV4dCBwYXJ0LiBZb3XigJlsbCB3YW50IHRvIHNhdmUgaXQgaW4gdGhlIGZvbGRlciB3aGVyZSB5b3UgY3JlYXRlIHlvdXIgc2hpbnkgYXBwLgoKYGBge3J9CmxlbmRpbmdfZmluYWxfc3RhY2sgPC0gbGVuZGluZ19ibGVuZCAlPiUgCiAgZml0X21lbWJlcnMoKQoKbGVuZGluZ19maW5hbF9zdGFjawoKc2F2ZVJEUyhsZW5kaW5nX2ZpbmFsX3N0YWNrLCBmaWxlID0gImxlbmRpbmdfZmluYWxfc3RhY2siKQpgYGAKCmBgYHtyfQpsZW5kaW5nX3N0YWNrX3Rlc3QgPC0gbGVuZGluZ190ZXN0ICU+JSAKICBiaW5kX2NvbHMocHJlZGljdChsZW5kaW5nX2ZpbmFsX3N0YWNrLCBuZXdfZGF0YSA9IGxlbmRpbmdfdGVzdCwgdHlwZSA9ICJwcm9iIikpICU+JSAKICBiaW5kX2NvbHMocHJlZGljdChsZW5kaW5nX2ZpbmFsX3N0YWNrLCBuZXdfZGF0YSA9IGxlbmRpbmdfdGVzdCkpCgpsZW5kaW5nX3N0YWNrX3Rlc3QgJT4lIAogIGFjY3VyYWN5KC5wcmVkX2NsYXNzLCBDbGFzcykKCmxlbmRpbmdfc3RhY2tfdGVzdCAlPiUgCiAgcm9jX2F1YyhDbGFzcywgLnByZWRfYmFkKQoKYXV0b3Bsb3Qocm9jX2N1cnZlKGxlbmRpbmdfc3RhY2tfdGVzdCwgQ2xhc3MsIC5wcmVkX2JhZCkpCmBgYAoKCgoKIyMgU2hpbnkgQXBwCgpUaGUgc2hpbnkgYXBwIGlzIG5vdCBmaW5pc2hlZCBidXQgYSBsaW5rIHRvIHRoZSBHaXRIdWIgcGFnZSBpcyBwcm92aWRlZCB0byBzaG93IHByb2dyZXNzLgoKKiBbSGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2FsZXhkZW56bGVyL1NUQVQ0OTRfSFcyX1NoaW55QXBwKSBpcyB0aGUgR2l0SHViIHJlcG9zaXRvcnkgZm9yIG15IFNoaW55IGFwcC4KPCEtLSAqIFtIZXJlXShJTlNFUlQgTElOSyBUTyBTSElOWSBBUFApIGlzIHRoZSBsaW5rIHRvIG15IFNoaW55IGFwcC4gLS0+CgojIyBDb2RlZCBCaWFzCiogVGhlIHBhcnQgb2YgdGhlIGZpbG0gdGhhdCBpbXBhY3RlZCBtZSB0aGUgbW9zdCB3YXMgdGhlIHBhcnQgd2hlcmUgdGhlIGhpZ2ggc2Nob29sIGFnZWQga2lkIHdhcyBzdG9wcGVkIG9uIHRoZSBzdHJlZXQgaW4gTG9uZG9uIGJlY2F1c2UgdGhlIGZhY2lhbCByZWNvZ25pdGlvbiB0ZWNobm9sb2d5IG1pc2lkZW50aWZpZWQgaGltIGR1ZSB0byBoaXMgcmFjZS4gSXQgcHJvdmlkZWQgbWUgZXZpZGVuY2UgZm9yIGhvdyBtdWNoIG9mIGEgcHJvYmxlbSB0aGlzIHRydWx5IGlzLiBJIHdhcyBtb3N0IHN1cnByaXNlZCBieSBob3cgd2lkZXNwcmVhZCBmYWNpYWwgcmVjb2duaXRpb24gd2FzIHVzZWQgaW4gQ2hpbmEsIGFuZCBob3cgQ2hpbmEgdXNlcyBpdCB0byBjcmVhdGUgYSBjaXRpemVuIHJhdGluZy4gSSB3YXMgYWxzbyBzdXJwcmlzZWQgYXQgaG93IHRoZSBVbml0ZWQgU3RhdGVzIGRvZXMgdGhpcyB3aXRob3V0IG5lYXJseSBhcyBtdWNoIHRyYW5zcGFyZW5jeSwgd2hpY2ggaXMgYSBodWdlIHByb2JsZW0gaW4gbXkgZXllcy4gSXQgd2FzIHRydWx5IHNob2NraW5nIGhvdyB0cmFuc3BhcmVudCBDaGluYSBpcyBhYm91dCBmYWNpYWwgcmVjb2duaXRpb24gc29mdHdhcmUgdXNlcywgYW5kIGhvdyBzb21lIGNpdGl6ZW5zIGFyZSBva2F5IHdpdGggd2hhdCBDaGluYSBpcyBkb2luZy4gSSB3YXMgYWxzbyBzaG9ja2VkIHRoYXQgdGhlcmUgaXMgbm8gQUkgb3IgTWFjaGluZSBMZWFybmluZyBBbGdvcml0aG0gcmVndWxhdGlvbiBpbiB0aGUgVW5pdGVkIFN0YXRlcy4gT3ZlcmFsbCwgSSBmZWx0IGdlbmVyYWxseSB1bnNldHRsZWQgYnkgdGhlIGRvY3VtZW50YXJ5LCBhcyBpdCBtYWRlIG1lIGZlYXIgZm9yIHRoZSBmdXR1cmUgb2YgYWxnb3JpdGhtIHVzYWdlIGluIHRoZSBVbml0ZWQgU3RhdGVzLgoKCgoKCgoK